package com.cloud.gatewaySpringSecurity.config;

import com.cloud.gatewaySpringSecurity.filter.JwtWebFilter;
import com.cloud.gatewaySpringSecurity.handler.*;
import com.cloud.gatewaySpringSecurity.properties.RsaKeyProperties;
import com.cloud.gatewaySpringSecurity.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;

import java.util.LinkedList;

/**
 * @author zhozixiong
 * @date 2023/03/02
 */
@EnableWebFluxSecurity
public class SpringSecurityConfig {

    /**
     * 登录请求参数处理器
     */
    @Autowired
    private PasswordHandler passwordHandler;

    @Autowired
    private EmailHandler emailHandler;

    @Autowired
    private PhoneHandler phoneHandler;

    /**
     * 登录处理器
     */
    @Autowired
    private PasswordAuthenticationManager passwordAuthenticationManager;

    @Autowired
    private EmailAuthenticationManager emailAuthenticationManager;

    @Autowired
    private PhoneAuthenticationManager phoneAuthenticationManager;

    /**
     * token处理器
     */
    @Autowired
    private ParseTokenHandler parseTokenHandler;

    /**
     * 登录成功处理器
     */
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    /**
     * 登录失败处理器
     */
    @Autowired
    private LoginFailureHandler loginFailureHandler;

    /**
     * 登出处理器
     */
    @Autowired
    private LogoutHandler logoutHandler;

    /**
     * 登出成功处理器
     */
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private AuthorizationManager authorizationManager;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Value("#{'${spring.cloud.gateway.login-url}'.split(',')}")
    private String[] loginUrls;

    @Value("#{'${spring.cloud.gateway.unnecessary-url}'.split(',')}")
    private String[] unnecessaryUrls;

    @Autowired
    private RsaKeyProperties rsaKeyProperties;

    @Autowired
    private RedisService redisService;

    @Bean
    public RsaKeyProperties rsaKeyProperties() {
        return new RsaKeyProperties();
    }

    @Bean
    public SecurityWebFilterChain usernamePasswordWebFilterChain(ServerHttpSecurity http) {
        http
                .authorizeExchange()
                //无需过滤路径
                .pathMatchers(HttpMethod.POST, loginUrls).permitAll()
                .pathMatchers(unnecessaryUrls).permitAll()
                //允许所有options
                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                //认证处理器
                .anyExchange().access(authorizationManager)
                .and()
                .addFilterAfter(new JwtWebFilter(rsaKeyProperties, redisService, loginUrls, unnecessaryUrls), SecurityWebFiltersOrder.FIRST)
                .securityContextRepository(parseTokenHandler)
                //登录处理器
                .addFilterAt(phoneWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(passwordWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(emailWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .formLogin().disable()
                .httpBasic().disable()
                //登出处理器
                .logout()
                .logoutUrl("/logout")
                .logoutHandler(logoutHandler)
                .logoutSuccessHandler(logoutSuccessHandler)
                .and()
                //权限认证失败处理器
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .and()
                //防止csrf攻击
                .csrf().disable()
                //跨域
                .cors().disable();
        return http.build();
    }

    private ReactiveAuthenticationManager getManager(ReactiveAuthenticationManager manager) {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(manager);
        return new DelegatingReactiveAuthenticationManager(managers);
    }

    /**
     *  邮箱登录
     */
    private AuthenticationWebFilter emailWebFilter() {
        AuthenticationWebFilter filter = new AuthenticationWebFilter(getManager(emailAuthenticationManager));
        filter.setServerAuthenticationConverter(emailHandler);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        filter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/emailLogin"));
        return filter;
    }

    /**
     *  账号密码登录
     */
    private AuthenticationWebFilter passwordWebFilter() {
        AuthenticationWebFilter filter = new AuthenticationWebFilter(getManager(passwordAuthenticationManager));
        filter.setServerAuthenticationConverter(passwordHandler);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        filter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/passwordLogin"));
        return filter;
    }

    /**
     *  手机验证码登录
     */
    private AuthenticationWebFilter phoneWebFilter() {
        AuthenticationWebFilter filter = new AuthenticationWebFilter(getManager(phoneAuthenticationManager));
        filter.setServerAuthenticationConverter(phoneHandler);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        filter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/phoneLogin"));
        return filter;
    }
}
